wartremoverでScalaコードを整える
はじめに
前回に引き続きScalaコードを整えていこうと思います。
wartremoverとは
wartremoverはScala用のlintツール(静的解析ツール)です。 公式サイトには以下のようにあります。
WartRemover takes the pain out of writing scala by removing some of the language’s nastier features.
Scalaの言語仕様には色々な機能が含まれているのでこうしたツールでチェックできるのは、あまりScalaに詳しくない自分としては助かります。
チェックの内容
wartremoverで行われる全てのチェックはこちらで説明されています。 下記でいくつか例を紹介します。
チェック例: ArrayEquals
下記の例のようにArrayどうしを ==
で比較するコードを検出します。
(Arrayの==
での比較は内容ではなく参照が一致するかを判定するので意図した比較が行われない可能性があるため)
List(1) == List(1) //true Array(1) == Array(1) //false, won't compile: == is disabled, use sameElements instead
チェック例: DefaultArguments
メソッドのデフォルト引数を検出します。 (デフォルト引数はメソッドを使うのを難しくするため)
// Won't compile: Function has default arguments def x(y: Int = 0)
チェック例: Equals
型安全ではないAny
の==
およびequals
, eq
, ne
の使用を検出します。
ドキュメントにあるようにImplicit class 内に ===
を定義するかCatsのEqを使いましょう。
"1" == 1 //コンパイルできるがチェックは失敗 // === を定義する @SuppressWarnings(Array("org.wartremover.warts.Equals")) implicit final class AnyOps[A](self: A) { def ===(other: A): Boolean = self == other } //またはCatsのEqを使う import cats.implicits._ "1" === "1" //ok
wartremoverの導入
project/plugins.sbt
に次の1行を追加します。
addSbtPlugin("org.wartremover" % "sbt-wartremover" % "2.4.2")
次にbuild.sbt
にチェックの詳細を定義します。
どのチェックをワーニング、エラーとするかをwartremoverWarnings
, wartremoverErrors
に指定することで設定します。
すべてのチェックをエラーにする場合
wartremoverErrors ++= Warts.unsafe
すべてのチェックをワーニングにする場合
wartremoverWarnings ++= Warts.unsafe
一部のチェックをワーニングにする場合
wartremoverErrors ++= Warts.allBut(Wart.Any, Wart.Nothing, Wart.Serializable) wartremoverWarnings += Wart.Nothing wartremoverWarnings ++= Seq(Wart.Any, Wart.Serializable)
コード中で明示的にチェックを抑制する
実際に開発していると、原則的にはチェックしたい項目だが「このクラスのこのメソッドだけは見逃して・・・」という箇所が出てきます。 そんな時には下記のようにアノテーションを付与することでチェックを抑制できます。
下記の例では Var
と Null
を抑制しています。
@SuppressWarnings(Array("org.wartremover.warts.Var", "org.wartremover.warts.Null")) var foo = null
使ってみる
上記の設定をした上でsbtからcompile
タスクを実行するとチェックが行われます。
以下のコードをコンパイルしてみます。
package object model { case class Name(name: String) case class Message(message: String) }
次のようにwartremover:FinalCaseClass
に違反していることが検出できました。
> sbt compile [info] Loading settings for project global-plugins from idea.sbt ... [info] Loading global plugins from /Users/sasaki.kazuhiro/.sbt/1.0/plugins [info] Loading settings for project scalafmt-example-build from plugins.sbt ... [info] Loading project definition from /Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/scalafmt-example/project [info] Loading settings for project scalafmt-example from build.sbt ... [info] Set current project to scalafmt-example (in build file:/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/scalafmt-example/) [info] Executing in batch mode. For better performance use sbt's shell [info] Compiling 1 Scala source to /Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/scalafmt-example/target/scala-2.12/classes ... [error] /Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/scalafmt-example/src/main/scala/model/package.scala:2:14: [wartremover:FinalCaseClass] case classes must be final [error] case class Name(name: String) [error] ^ [error] /Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/scalafmt-example/src/main/scala/model/package.scala:3:14: [wartremover:FinalCaseClass] case classes must be final [error] case class Message(message: String) [error] ^ [error] two errors found [error] (Compile / compileIncremental) Compilation failed [error] Total time: 6 s, completed 2019/08/21 16:59:35
case classes must be final
と怒られたので、次のように修正してみるとコンパイルが成功します。
package object model { final case class Name(name: String) final case class Message(message: String) }
> sbt compile [info] Loading settings for project global-plugins from idea.sbt ... [info] Loading global plugins from /Users/sasaki.kazuhiro/.sbt/1.0/plugins [info] Loading settings for project scalafmt-example-build from plugins.sbt ... [info] Loading project definition from /Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/scalafmt-example/project [info] Loading settings for project scalafmt-example from build.sbt ... [info] Set current project to scalafmt-example (in build file:/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/scalafmt-example/) [info] Executing in batch mode. For better performance use sbt's shell [success] Total time: 1 s, completed 2019/08/21 17:04:26
CircleCI上でチェックする
ここまで設定しているなら追加の設定は必要ありません。sbt compileが実行された時にチェックが行われます。
まとめ
今回も整いました。
おまけ wartremover-contrib
ビルトインのチェックに加えてwartremover-contrib で提供されているチェックを使用することもできます。